/*-------------------------------------------------------------------------
 *
 * nodeMergejoin.c
 *	  支持合并连接的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeMergejoin.c
 *
 *-------------------------------------------------------------------------
 */
/*
 * 接口例程
 *		ExecMergeJoin			合并外部和内部关系。
 *		ExecInitMergeJoin		创建和初始化运行时状态
 *		ExecEndMergeJoin		清理节点。
 *
 * 注意事项
 *
 *		合并连接是通过连接满足连接子句形式的内部和外部元组来实现的 ((= outerKey innerKey) ...).
 *		连接子句列表由查询规划器提供，可以包含多个(= outerKey innerKey)子句（用于复合排序键）。
 *
 *		然而，查询执行器需要知道外部元组是否“更大/更小”于内部元组，以便能够“同步”这两种关系。例如，考虑以下关系：
 *
 *				外部: (0 ^1 1 2 5 5 5 6 6 7)	当前元组: 1
 *				内部: (1 ^3 5 5 5 5 6)		 当前元组: 3
 *
 *		为了继续合并连接，执行器需要扫描内部和外部关系，直到匹配元组5。它需要知道当前的内部元组3“更大”于外部元组1，因此它应该首先扫描外部关系以找到匹配的元组，依此类推。
 *
 *		因此，我们不是直接执行合并连接子句，而是分别评估左侧和右侧键表达式，然后逐列比较（见MJCompare）。规划器向我们传递了足够的信息，关于输入的排序顺序，以使我们能够确定如何进行比较。我们可以使用适当的btree比较函数，因为Postgres只有通过btree运算符族指定的排序概念。
 *
 *
 *		考虑上述关系，并假设执行器刚刚将第一个外部“5”与最后一个内部“5”连接。下一步当然是将第二个外部“5”与所有内部“5”连接。这需要重新定位内部“光标”，使其指向第一个内部“5”。这通过“标记”第一个内部5来实现，以便我们可以在与第二个外部5连接之前恢复“光标”。访问方法接口提供了标记和恢复元组的例程。
 *
 *
 *		合并连接算法的基本操作如下：
 *
 *		合并 {
 *			获取初始外部和内部元组				初始化
 *			永远执行 {
 *				当 (外部 != 内部) {					跳过测试
 *					如果 (外部 < 内部)
 *						推进外部						跳过外部推进
 *					否则
 *						推进内部						跳过内部推进
 *				}
 *				标记内部位置							跳过测试
 *				永远执行 {
 *					当 (外部 == 内部) {
 *						连接元组							连接元组
 *						推进内部位置						下一个内部
 *					}
 *					推进外部位置						下一个外部
 *					如果 (外部 == 标记)					测试外部
 *						将内部位置恢复至标记			测试外部
 *					否则
 *						中断	// 返回外部循环顶部
 *				}
 *			}
 *		}
 *
 *		合并连接操作是以状态机的方式编码的。在每个状态，我们执行某些操作，然后进入另一状态。该状态存储在节点的执行状态信息中，并在对ExecMergeJoin的调用中保留。
 */
#include "postgres.h"

#include "access/nbtree.h"
#include "executor/execdebug.h"
#include "executor/nodeMergejoin.h"
#include "miscadmin.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"

/*
 * ExecMergeJoin状态机的状态
 */
#define EXEC_MJ_INITIALIZE_OUTER		1
#define EXEC_MJ_INITIALIZE_INNER		2
#define EXEC_MJ_JOINTUPLES				3
#define EXEC_MJ_NEXTOUTER				4
#define EXEC_MJ_TESTOUTER				5
#define EXEC_MJ_NEXTINNER				6
#define EXEC_MJ_SKIP_TEST				7
#define EXEC_MJ_SKIPOUTER_ADVANCE		8
#define EXEC_MJ_SKIPINNER_ADVANCE		9
#define EXEC_MJ_ENDOUTER				10
#define EXEC_MJ_ENDINNER				11

/*
 * 每个合并连接子句的运行时数据
 */
typedef struct MergeJoinClauseData
{
	/* 可执行表达式树 */
	ExprState  *lexpr;			/* 左侧（外部）输入表达式 */
	ExprState  *rexpr;			/* 右侧（内部）输入表达式 */

	/*
	 * 如果我们有当前的左或右输入元组，表达式的值被加载到这些字段中：
	 */
	Datum		ldatum;			/* 当前左边的值 */
	Datum		rdatum;			/* 当前右边的值 */
	bool		lisnull;		/* 及其 isnull 标志 */
	bool		risnull;

	/*
	 * 我们所需的关于比较左值和右值的信息都存储在这里。
	 */
	SortSupportData ssup;
}			MergeJoinClauseData;

/* MJEvalOuterValues 和 MJEvalInnerValues 的结果类型 */
typedef enum
{
	MJEVAL_MATCHABLE,			/* 正常的，可能可以匹配的元组 */
	MJEVAL_NONMATCHABLE,		/* 元组无法连接，因为它有一个空值 */
	MJEVAL_ENDOFJOIN			/* 输入结束（物理或有效） */
} MJEvalResult;


#define MarkInnerTuple(innerTupleSlot, mergestate) \
	ExecCopySlot((mergestate)->mj_MarkedTupleSlot, (innerTupleSlot))


/*
 * MJExamineQuals
 *
 * 这将解构可合并连接表达式的列表，列表由规划器以 "leftexpr = rightexpr"
 * 表达式树的形式提供，这些表达式树的顺序与输入的排序列匹配。
 * 我们构建一个包含我们在运行时需要的信息的 MergeJoinClause 结构数组。
 * 每个结构本质上告诉我们如何比较原始子句中的两个表达式。
 *
 * 除了表达式本身，规划器还传递了 btree 操作族 OID、排序规则 OID、btree 策略编号
 * (BTLessStrategyNumber 或 BTGreaterStrategyNumber) 和 nulls-first 标志，这些标志标识
 * 每个合并键的预期排序顺序。可合并的连接操作符是操作族中的等式操作符，两个输入
 * 根据操作族和排序规则在递增或递减（分别）的顺序中保证有序，空值位于范围的指示端。
 * 这使我们能够从操作族中获得所需的比较函数。
 */
static MergeJoinClause
fc_MJExamineQuals(List *fc_mergeclauses,
			   Oid *fc_mergefamilies,
			   Oid *fc_mergecollations,
			   int *fc_mergestrategies,
			   bool *fc_mergenullsfirst,
			   PlanState *fc_parent)
{
	MergeJoinClause fc_clauses;
	int			fc_nClauses = list_length(fc_mergeclauses);
	int			fc_iClause;
	ListCell   *fc_cl;

	fc_clauses = (MergeJoinClause) palloc0(fc_nClauses * sizeof(MergeJoinClauseData));

	fc_iClause = 0;
	foreach(fc_cl, fc_mergeclauses)
	{
		OpExpr	   *fc_qual = (OpExpr *) lfirst(fc_cl);
		MergeJoinClause fc_clause = &fc_clauses[fc_iClause];
		Oid			fc_opfamily = fc_mergefamilies[fc_iClause];
		Oid			fc_collation = fc_mergecollations[fc_iClause];
		StrategyNumber fc_opstrategy = fc_mergestrategies[fc_iClause];
		bool		fc_nulls_first = fc_mergenullsfirst[fc_iClause];
		int			fc_op_strategy;
		Oid			fc_op_lefttype;
		Oid			fc_op_righttype;
		Oid			fc_sortfunc;

		if (!IsA(fc_qual, OpExpr))
			elog(ERROR, "mergejoin clause is not an OpExpr");

		/*
		 * 准备输入表达式以供执行。
		 */
		fc_clause->lexpr = ExecInitExpr((Expr *) linitial(fc_qual->args), fc_parent);
		fc_clause->rexpr = ExecInitExpr((Expr *) lsecond(fc_qual->args), fc_parent);

		/* 设置排序支持数据 */
		fc_clause->ssup.ssup_cxt = CurrentMemoryContext;
		fc_clause->ssup.ssup_collation = fc_collation;
		if (fc_opstrategy == BTLessStrategyNumber)
			fc_clause->ssup.ssup_reverse = false;
		else if (fc_opstrategy == BTGreaterStrategyNumber)
			fc_clause->ssup.ssup_reverse = true;
		else					/* 规划器搞错了 */
			elog(ERROR, "unsupported mergejoin strategy %d", fc_opstrategy);
		fc_clause->ssup.ssup_nulls_first = fc_nulls_first;

		/* 提取操作符声明的左/右数据类型 */
		get_op_opfamily_properties(fc_qual->opno, fc_opfamily, false,
								   &fc_op_strategy,
								   &fc_op_lefttype,
								   &fc_op_righttype);
		if (fc_op_strategy != BTEqualStrategyNumber)	/* 不应该发生 */
			elog(ERROR, "cannot merge using non-equality operator %u",
				 fc_qual->opno);

		/*
		 * 排序支持例程必须知道缩写优化原则上是否适用。
		 * 在合并连接中，它永远不适用，因为没有方便的机会转换为
		 * 另一种表示形式。
		 */
		fc_clause->ssup.abbreviate = false;

		/* 并获取匹配的支持或比较函数 */
		Assert(fc_clause->ssup.comparator == NULL);
		fc_sortfunc = get_opfamily_proc(fc_opfamily,
									 fc_op_lefttype,
									 fc_op_righttype,
									 BTSORTSUPPORT_PROC);
		if (OidIsValid(fc_sortfunc))
		{
			/* 排序支持函数可以提供一个比较器 */
			OidFunctionCall1(fc_sortfunc, PointerGetDatum(&fc_clause->ssup));
		}
		if (fc_clause->ssup.comparator == NULL)
		{
			/* 不可用支持，获取比较函数 */
			fc_sortfunc = get_opfamily_proc(fc_opfamily,
										 fc_op_lefttype,
										 fc_op_righttype,
										 BTORDER_PROC);
			if (!OidIsValid(fc_sortfunc))	/* 不应该发生 */
				elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
					 BTORDER_PROC, fc_op_lefttype, fc_op_righttype, fc_opfamily);
			/* 我们将使用适配函数来调用旧式 btree 比较器 */
			PrepareSortSupportComparisonShim(fc_sortfunc, &fc_clause->ssup);
		}

		fc_iClause++;
	}

	return fc_clauses;
}

/*
 * MJEvalOuterValues
 *
 * 计算当前外部元组的合并连接表达式的值。
 * 我们还检测当前外部元组是否与任何内容匹配——
 * 如果它产生了一个 NULL 输入，这种情况成立，因为我们假设
 * 合并连接操作符是严格的。如果 NULL 在第一个连接列中，并且该列
 * 将空值排在最后，那么我们可以进一步推断，后续的元组也无法匹配任何内容，
 * 因为它们必须在第一列中全部具有空值。然而，这种情况只有在我们不处于
 * FillOuter 模式时才有意义，否则我们无论如何必须访问所有元组。
 *
 * 为了方便调用者，我们还使这个例程负责检测输入结束（空外部元组），
 * 并在看到时返回 MJEVAL_ENDOFJOIN。这使同样的代码可以用于实际的
 * 输入结束和由第一列 NULL 表示的有效输入结束。
 *
 * 我们在 OuterEContext 中评估值，每次移动到新元组时都可以重置它。
 */
static MJEvalResult
fc_MJEvalOuterValues(MergeJoinState *fc_mergestate)
{
	ExprContext *fc_econtext = fc_mergestate->mj_OuterEContext;
	MJEvalResult fc_result = MJEVAL_MATCHABLE;
	int			fc_i;
	MemoryContext fc_oldContext;

	/* 检查外部子计划是否结束 */
	if (TupIsNull(fc_mergestate->mj_OuterTupleSlot))
		return MJEVAL_ENDOFJOIN;

	ResetExprContext(fc_econtext);

	fc_oldContext = MemoryContextSwitchTo(fc_econtext->ecxt_per_tuple_memory);

	fc_econtext->ecxt_outertuple = fc_mergestate->mj_OuterTupleSlot;

	for (fc_i = 0; fc_i < fc_mergestate->mj_NumClauses; fc_i++)
	{
		MergeJoinClause fc_clause = &fc_mergestate->mj_Clauses[fc_i];

		fc_clause->ldatum = ExecEvalExpr(fc_clause->lexpr, fc_econtext,
									  &fc_clause->lisnull);
		if (fc_clause->lisnull)
		{
			/* 匹配不可能；我们可以提前结束连接吗？ */
			if (fc_i == 0 && !fc_clause->ssup.ssup_nulls_first &&
				!fc_mergestate->mj_FillOuter)
				fc_result = MJEVAL_ENDOFJOIN;
			else if (fc_result == MJEVAL_MATCHABLE)
				fc_result = MJEVAL_NONMATCHABLE;
		}
	}

	MemoryContextSwitchTo(fc_oldContext);

	return fc_result;
}

/*
 * MJEvalInnerValues
 *
 * 与上述相同，但针对内部元组。在这里，我们必须准备从
 * 真实当前内部元组或标记的内部元组中加载数据，
 * 因此调用者必须告诉我们要从哪个槽加载。
 */
static MJEvalResult
fc_MJEvalInnerValues(MergeJoinState *fc_mergestate, TupleTableSlot *fc_innerslot)
{
	ExprContext *fc_econtext = fc_mergestate->mj_InnerEContext;
	MJEvalResult fc_result = MJEVAL_MATCHABLE;
	int			fc_i;
	MemoryContext fc_oldContext;

	/* 检查内部子计划是否结束 */
	if (TupIsNull(fc_innerslot))
		return MJEVAL_ENDOFJOIN;

	ResetExprContext(fc_econtext);

	fc_oldContext = MemoryContextSwitchTo(fc_econtext->ecxt_per_tuple_memory);

	fc_econtext->ecxt_innertuple = fc_innerslot;

	for (fc_i = 0; fc_i < fc_mergestate->mj_NumClauses; fc_i++)
	{
		MergeJoinClause fc_clause = &fc_mergestate->mj_Clauses[fc_i];

		fc_clause->rdatum = ExecEvalExpr(fc_clause->rexpr, fc_econtext,
									  &fc_clause->risnull);
		if (fc_clause->risnull)
		{
			/* 匹配不可能；我们可以提前结束连接吗？ */
			if (fc_i == 0 && !fc_clause->ssup.ssup_nulls_first &&
				!fc_mergestate->mj_FillInner)
				fc_result = MJEVAL_ENDOFJOIN;
			else if (fc_result == MJEVAL_MATCHABLE)
				fc_result = MJEVAL_NONMATCHABLE;
		}
	}

	MemoryContextSwitchTo(fc_oldContext);

	return fc_result;
}

/*
 * MJCompare
 *
 * 比较当前两个输入元组的可合并连接值，如果相等则返回 0
 * （即，所有合并连接等式都成功），如果外部 > 内部则返回 >0，
 * 如果外部 < 内部则返回 <0。
 *
 * MJEvalOuterValues 和 MJEvalInnerValues 必须已经分别为
 * 当前外部和内部元组调用。
 */
static int fc_MJCompare(MergeJoinState *fc_mergestate)
{
	int			fc_result = 0;
	bool		fc_nulleqnull = false;
	ExprContext *fc_econtext = fc_mergestate->js.ps.ps_ExprContext;
	int			fc_i;
	MemoryContext fc_oldContext;

	/*
	 * 在短暂的上下文中调用比较函数，以防它们泄漏
	 * 内存。
	 */
	ResetExprContext(fc_econtext);

	fc_oldContext = MemoryContextSwitchTo(fc_econtext->ecxt_per_tuple_memory);

	for (fc_i = 0; fc_i < fc_mergestate->mj_NumClauses; fc_i++)
	{
		MergeJoinClause fc_clause = &fc_mergestate->mj_Clauses[fc_i];

		/*
		 * NULL 对 NULL 的特殊情况，否则使用标准比较。
		 */
		if (fc_clause->lisnull && fc_clause->risnull)
		{
			fc_nulleqnull = true;	/* NULL "=" NULL */
			continue;
		}

		fc_result = ApplySortComparator(fc_clause->ldatum, fc_clause->lisnull,
									 fc_clause->rdatum, fc_clause->risnull,
									 &fc_clause->ssup);

		if (fc_result != 0)
			break;
	}

	/*
	 * 如果我们有任何 NULL 对 NULL 的输入，我们不想报告
	 * 元组相等。相反，如果结果仍为 0，将其更改为 +1。这样将
	 * 导致连接的内部侧向前推进。
	 *
	 * 同样，如果存在常量假连接质量，则不报告
	 * 相等。我们必须将其作为合并等式的一部分进行检查，
	 * 否则重新扫描逻辑将会出错。
	 */
	if (fc_result == 0 &&
		(fc_nulleqnull || fc_mergestate->mj_ConstFalseJoin))
		fc_result = 1;

	MemoryContextSwitchTo(fc_oldContext);

	return fc_result;
}


/*
 * 使用 NULL 为内部元组生成一个虚假的连接元组，
 * 如果它通过非连接质量，则返回该元组。
 */
static TupleTableSlot * fc_MJFillOuter(MergeJoinState *fc_node)
{
	ExprContext *fc_econtext = fc_node->js.ps.ps_ExprContext;
	ExprState  *fc_otherqual = fc_node->js.ps.qual;

	ResetExprContext(fc_econtext);

	fc_econtext->ecxt_outertuple = fc_node->mj_OuterTupleSlot;
	fc_econtext->ecxt_innertuple = fc_node->mj_NullInnerTupleSlot;

	if (ExecQual(fc_otherqual, fc_econtext))
	{
		/*
		 * 资格验证成功。现在形成所需的投影元组并返回包含它的槽。
		 */
		MJ_printf("ExecMergeJoin: returning outer fill tuple\n");

		return ExecProject(fc_node->js.ps.ps_ProjInfo);
	}
	else
		InstrCountFiltered2(fc_node, 1);

	return NULL;
}

/*
 * 生成一个假的连接元组，外部元组为null，如果它通过非连接资格，则返回它。
 */
static TupleTableSlot * fc_MJFillInner(MergeJoinState *fc_node)
{
	ExprContext *fc_econtext = fc_node->js.ps.ps_ExprContext;
	ExprState  *fc_otherqual = fc_node->js.ps.qual;

	ResetExprContext(fc_econtext);

	fc_econtext->ecxt_outertuple = fc_node->mj_NullOuterTupleSlot;
	fc_econtext->ecxt_innertuple = fc_node->mj_InnerTupleSlot;

	if (ExecQual(fc_otherqual, fc_econtext))
	{
		/*
		 * 资格验证成功。现在形成所需的投影元组并返回包含它的槽。
		 */
		MJ_printf("ExecMergeJoin: returning inner fill tuple\n");

		return ExecProject(fc_node->js.ps.ps_ProjInfo);
	}
	else
		InstrCountFiltered2(fc_node, 1);

	return NULL;
}


/*
 * 检查一个资格条件是否是常量真或常量假。
 * 如果是常量假（或null），则将 *is_const_false 设置为真。
 *
 * 常量真通常用 NIL 列表表示，但我们也允许实际的 bool Const。我们确实期望规划者会丢弃与常量假 ANDed 的任何非常量项。
 */
static bool fc_check_constant_qual(List *fc_qual, bool *fc_is_const_false)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_qual)
	{
		Const	   *fc_con = (Const *) lfirst(fc_lc);

		if (!fc_con || !IsA(fc_con, Const))
			return false;
		if (fc_con->constisnull || !DatumGetBool(fc_con->constvalue))
			*fc_is_const_false = true;
	}
	return true;
}


/* ----------------------------------------------------------------
 *		ExecMergeTupleDump
 *
 *		当定义了 EXEC_MERGEJOINDEBUG 时，通过 MJ_dump() 宏调用此函数
 * ----------------------------------------------------------------
 */
#ifdef EXEC_MERGEJOINDEBUG

static void fc_ExecMergeTupleDumpOuter(MergeJoinState *fc_mergestate)
{
	TupleTableSlot *fc_outerSlot = fc_mergestate->mj_OuterTupleSlot;

	printf("==== outer tuple ====\n");
	if (TupIsNull(fc_outerSlot))
		printf("(nil)\n");
	else
		MJ_debugtup(fc_outerSlot);
}

static void fc_ExecMergeTupleDumpInner(MergeJoinState *fc_mergestate)
{
	TupleTableSlot *fc_innerSlot = fc_mergestate->mj_InnerTupleSlot;

	printf("==== inner tuple ====\n");
	if (TupIsNull(fc_innerSlot))
		printf("(nil)\n");
	else
		MJ_debugtup(fc_innerSlot);
}

static void fc_ExecMergeTupleDumpMarked(MergeJoinState *fc_mergestate)
{
	TupleTableSlot *fc_markedSlot = fc_mergestate->mj_MarkedTupleSlot;

	printf("==== marked tuple ====\n");
	if (TupIsNull(fc_markedSlot))
		printf("(nil)\n");
	else
		MJ_debugtup(fc_markedSlot);
}

static void ExecMergeTupleDump(MergeJoinState *fc_mergestate)
{
	printf("******** ExecMergeTupleDump ********\n");

	fc_ExecMergeTupleDumpOuter(fc_mergestate);
	fc_ExecMergeTupleDumpInner(fc_mergestate);
	fc_ExecMergeTupleDumpMarked(fc_mergestate);

	printf("********\n");
}
#endif

/* ----------------------------------------------------------------
 *		ExecMergeJoin
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_ExecMergeJoin(PlanState *fc_pstate)
{
	MergeJoinState *fc_node = castNode(MergeJoinState, fc_pstate);
	ExprState  *fc_joinqual;
	ExprState  *fc_otherqual;
	bool		fc_qualResult;
	int			fc_compareResult;
	PlanState  *fc_innerPlan;
	TupleTableSlot *fc_innerTupleSlot;
	PlanState  *fc_outerPlan;
	TupleTableSlot *fc_outerTupleSlot;
	ExprContext *fc_econtext;
	bool		fc_doFillOuter;
	bool		fc_doFillInner;

	CHECK_FOR_INTERRUPTS();

	/*
	 * 从节点获取信息
	 */
	fc_innerPlan = innerPlanState(fc_node);
	fc_outerPlan = outerPlanState(fc_node);
	fc_econtext = fc_node->js.ps.ps_ExprContext;
	fc_joinqual = fc_node->js.joinqual;
	fc_otherqual = fc_node->js.ps.qual;
	fc_doFillOuter = fc_node->mj_FillOuter;
	fc_doFillInner = fc_node->mj_FillInner;

	/*
	 * 重置每个元组的内存上下文，以释放在上一个元组周期中分配的任何表达式评估
	 * 存储。
	 */
	ResetExprContext(fc_econtext);

	/*
	 * 好的，一切都已设置.. 现在开始工作
	 */
	for (;;)
	{
		MJ_dump(fc_node);

		/*
		 * 获取连接的当前状态并相应地进行操作。
		 */
		switch (fc_node->mj_JoinState)
		{
				/*
				 * EXEC_MJ_INITIALIZE_OUTER 表示这是第一次调用
				 * ExecMergeJoin()，所以我们必须获取外部和内部子计划的
				 * 第一个可匹配元组。我们在 INITIALIZE_OUTER 状态处理外部部分，
				 * 然后为内部子计划进入 INITIALIZE_INNER 状态。
				 */
			case EXEC_MJ_INITIALIZE_OUTER:
				MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE_OUTER\n");

				fc_outerTupleSlot = ExecProcNode(fc_outerPlan);
				fc_node->mj_OuterTupleSlot = fc_outerTupleSlot;

				/* 计算连接值并检查是否不可匹配 */
				switch (fc_MJEvalOuterValues(fc_node))
				{
					case MJEVAL_MATCHABLE:
						/* 可以去获取第一个内部元组 */
						fc_node->mj_JoinState = EXEC_MJ_INITIALIZE_INNER;
						break;
					case MJEVAL_NONMATCHABLE:
						/* 保持在同一状态以获取下一个外部元组 */
						if (fc_doFillOuter)
						{
							/*
							 * 生成一个假的连接元组，内部元组为null，如果它通过
							 * 非连接资格，则返回它。
							 */
							TupleTableSlot *fc_result;

							fc_result = fc_MJFillOuter(fc_node);
							if (fc_result)
								return fc_result;
						}
						break;
					case MJEVAL_ENDOFJOIN:
						/* 没有更多的外部元组 */
						MJ_printf("ExecMergeJoin: nothing in outer subplan\n");
						if (fc_doFillInner)
						{
							/*
							 * 需要为剩余的内部元组发出右连接元组。我们将
							 * MatchedInner 设置为 true，以强制 ENDOUTER 状态
							 * 使内部推进。
							 */
							fc_node->mj_JoinState = EXEC_MJ_ENDOUTER;
							fc_node->mj_MatchedInner = true;
							break;
						}
						/* 否则我们完成了。 */
						return NULL;
				}
				break;

			case EXEC_MJ_INITIALIZE_INNER:
				MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE_INNER\n");

				fc_innerTupleSlot = ExecProcNode(fc_innerPlan);
				fc_node->mj_InnerTupleSlot = fc_innerTupleSlot;

				/* 计算连接值并检查是否不可匹配 */
				switch (fc_MJEvalInnerValues(fc_node, fc_innerTupleSlot))
				{
					case MJEVAL_MATCHABLE:

						/*
						 * 好的，我们得到了初始元组。首先跳过
						 * 不匹配的元组。
						 */
						fc_node->mj_JoinState = EXEC_MJ_SKIP_TEST;
						break;
					case MJEVAL_NONMATCHABLE:
						/* 如果需要，标记后再推进 */
						if (fc_node->mj_ExtraMarks)
							ExecMarkPos(fc_innerPlan);
						/* 保持在同一状态以获取下一个内部元组 */
						if (fc_doFillInner)
						{
							/*
							 * 生成一个假的连接元组，外部元组为null，如果它通过
							 * 非连接资格，则返回它。
							 */
							TupleTableSlot *fc_result;

							fc_result = fc_MJFillInner(fc_node);
							if (fc_result)
								return fc_result;
						}
						break;
					case MJEVAL_ENDOFJOIN:
						/* 没有更多的内部元组 */
						MJ_printf("ExecMergeJoin: nothing in inner subplan\n");
						if (fc_doFillOuter)
						{
							/*
							 * 需要为所有外部元组，包括我们刚刚获取的那个，发出左连接元组。
							 * 我们将 MatchedOuter 设置为 false，以强制 ENDINNER
							 * 状态在推进外部之前发出第一个元组。
							 */
							fc_node->mj_JoinState = EXEC_MJ_ENDINNER;
							fc_node->mj_MatchedOuter = false;
							break;
						}
						/* 否则我们完成了。 */
						return NULL;
				}
				break;

				/*
				 * EXEC_MJ_JOINTUPLES 表示我们有两个满足
				 * 合并条件的元组，所以我们连接它们，然后继续获取
				 * 下一个内部元组 (EXEC_MJ_NEXTINNER)。
				 */
			case EXEC_MJ_JOINTUPLES:
				MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTUPLES\n");

				/*
				 * 设置下一个状态机状态。无论我们返回这个连接元组还是
				 * 继续状态机执行，都会发生正确的事情。
				 */
				fc_node->mj_JoinState = EXEC_MJ_NEXTINNER;

				/*
				 * 检查额外的资格条件，以查看我们是否真的想返回
				 * 这个连接元组。如果不想，可以继续合并。
				 * 我们必须区分额外的连接资格（必须通过以考虑元组
				 * "匹配" 用于外连接逻辑）与其他资格（必须通过后我们才
				 * 实际返回元组）。
				 *
				 * 我们在这里不打算进行 ResetExprContext，假设我们在检查
				 * 合并资格时刚做过一次。每个元组执行一次应该足够。
				 * 我们确实必须为 ExecQual 使用设置上下文指向元组的链接。
				 */
				fc_outerTupleSlot = fc_node->mj_OuterTupleSlot;
				fc_econtext->ecxt_outertuple = fc_outerTupleSlot;
				fc_innerTupleSlot = fc_node->mj_InnerTupleSlot;
				fc_econtext->ecxt_innertuple = fc_innerTupleSlot;

				fc_qualResult = (fc_joinqual == NULL ||
							  ExecQual(fc_joinqual, fc_econtext));
				MJ_DEBUG_QUAL(fc_joinqual, fc_qualResult);

				if (fc_qualResult)
				{
					fc_node->mj_MatchedOuter = true;
					fc_node->mj_MatchedInner = true;

					/* 在反连接中，我们从不返回匹配的元组 */
					if (fc_node->js.jointype == JOIN_ANTI)
					{
						fc_node->mj_JoinState = EXEC_MJ_NEXTOUTER;
						break;
					}

					/*
					 * 如果我们只需要连接到第一个匹配的内部元组，
					 * 那么考虑返回这个，但之后继续下一个外部元组。
					 */
					if (fc_node->js.single_match)
						fc_node->mj_JoinState = EXEC_MJ_NEXTOUTER;

					fc_qualResult = (fc_otherqual == NULL ||
								  ExecQual(fc_otherqual, fc_econtext));
					MJ_DEBUG_QUAL(fc_otherqual, fc_qualResult);

					if (fc_qualResult)
					{
						/*
						 * 资格验证成功。现在形成所需的
						 * 投影元组并返回包含它的槽。
						 */
						MJ_printf("ExecMergeJoin: returning tuple\n");

						return ExecProject(fc_node->js.ps.ps_ProjInfo);
					}
					else
						InstrCountFiltered2(fc_node, 1);
				}
				else
					InstrCountFiltered1(fc_node, 1);
				break;

				/*
				 * EXEC_MJ_NEXTINNER 意味着将内部扫描推进到下一个
				 * 元组。如果元组不为nil，则继续测试它
				 * 与连接资格。
				 *
				 * 在推进之前，我们检查是否必须为这个内部元组发出
				 * 外连接填充元组。
				 */
			case EXEC_MJ_NEXTINNER:
				MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTINNER\n");

				if (fc_doFillInner && !fc_node->mj_MatchedInner)
				{
					/*
					 * 生成一个假的连接元组，外部元组为null，如果它通过
					 * 非连接资格，则返回它。
					 */
					TupleTableSlot *fc_result;

					fc_node->mj_MatchedInner = true;	/* 只做一次 */

					fc_result = fc_MJFillInner(fc_node);
					if (fc_result)
						return fc_result;
				}

				/*
				 * 现在我们获取下一个内部元组（如果有的话）。如果没有，
				 * 则推进到下一个外部元组（可能与
				 * 先前标记的元组连接）。
				 *
				 * 注意：这里绝不能做“额外标记”，因为我们可能需要
				 * 返回到先前标记的元组。
				 */
				fc_innerTupleSlot = ExecProcNode(fc_innerPlan);
				fc_node->mj_InnerTupleSlot = fc_innerTupleSlot;
				MJ_DEBUG_PROC_NODE(fc_innerTupleSlot);
				fc_node->mj_MatchedInner = false;

				/* 计算连接值并检查是否不可匹配 */
				switch (fc_MJEvalInnerValues(fc_node, fc_innerTupleSlot))
				{
					case MJEVAL_MATCHABLE:

						/*
						 * 测试新的内部元组以查看其是否与外部匹配。
						 *
						 * 如果它们匹配，则我们将它们连接并继续
						 * 下一个内部元组 (EXEC_MJ_JOINTUPLES)。
						 *
						 * 如果它们不匹配，则前进到下一个外部
						 * 元组。
						 */
						fc_compareResult = fc_MJCompare(fc_node);
						MJ_DEBUG_COMPARE(fc_compareResult);

						if (fc_compareResult == 0)
							fc_node->mj_JoinState = EXEC_MJ_JOINTUPLES;
						else if (fc_compareResult < 0)
							fc_node->mj_JoinState = EXEC_MJ_NEXTOUTER;
						else	/* compareResult > 0 不应该发生 */
							elog(ERROR, "mergejoin input data is out of order");
						break;
					case MJEVAL_NONMATCHABLE:

						/*
						 * 它包含一个 NULL，因此无法匹配任何外部
						 * 元组，所以我们可以跳过比较并假设
						 * 新元组大于当前外部元组。
						 */
						fc_node->mj_JoinState = EXEC_MJ_NEXTOUTER;
						break;
					case MJEVAL_ENDOFJOIN:

						/*
						 * 不再有内部元组。然而，这可能只是
						 * 有效的，而不是内部计划的物理结束，因此
						 * 强制将 mj_InnerTupleSlot 设置为 null，以确保我们
						 * 不再获取更多内部元组。（我们需要这个黑客
						 * 因为我们没有过渡到假定内部计划已经耗尽的状态。）
						 */
						fc_node->mj_InnerTupleSlot = NULL;
						fc_node->mj_JoinState = EXEC_MJ_NEXTOUTER;
						break;
				}
				break;

				/*-------------------------------------------
				 * EXEC_MJ_NEXTOUTER 意味着
				 *
				 *				外部 内部
				 * 外部元组 -  5		5  - 标记元组
				 *				  5		5
				 *				  6		6  - 内部元组
				 *				  7		7
				 *
				 * 我们知道我们刚刚遇到
				 * 第一个内部元组 > 当前外部元组（或者可能
				 * 内部流的结束）
				 * 所以获取一个新的外部元组，然后
				 * 继续测试它与标记元组的匹配
				 * (EXEC_MJ_TESTOUTER)
				 *
				 * 在前进之前，我们检查是否必须为这个外部元组发出
				 * 外连接填充元组。
				 *------------------------------------------------
				 */
			case EXEC_MJ_NEXTOUTER:
				MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTOUTER\n");

				if (fc_doFillOuter && !fc_node->mj_MatchedOuter)
				{
					/*
					 * 生成一个虚假的连接元组，对于内部元组使用空值，
					 * 如果它通过非连接条件，则返回它。
					 */
					TupleTableSlot *fc_result;

					fc_node->mj_MatchedOuter = true;	/* 只做一次 */

					fc_result = fc_MJFillOuter(fc_node);
					if (fc_result)
						return fc_result;
				}

				/*
				 * 现在我们获取下一个外部元组（如果有）
				 */
				fc_outerTupleSlot = ExecProcNode(fc_outerPlan);
				fc_node->mj_OuterTupleSlot = fc_outerTupleSlot;
				MJ_DEBUG_PROC_NODE(fc_outerTupleSlot);
				fc_node->mj_MatchedOuter = false;

				/* 计算连接值并检查是否不可匹配 */
				switch (fc_MJEvalOuterValues(fc_node))
				{
					case MJEVAL_MATCHABLE:
						/* 去测试新的元组与标记元组的匹配 */
						fc_node->mj_JoinState = EXEC_MJ_TESTOUTER;
						break;
					case MJEVAL_NONMATCHABLE:
						/* 不能匹配，因此获取下一个外部元组 */
						fc_node->mj_JoinState = EXEC_MJ_NEXTOUTER;
						break;
					case MJEVAL_ENDOFJOIN:
						/* 没有更多的外部元组 */
						MJ_printf("ExecMergeJoin: end of outer subplan\n");
						fc_innerTupleSlot = fc_node->mj_InnerTupleSlot;
						if (fc_doFillInner && !TupIsNull(fc_innerTupleSlot))
						{
							/*
							 * 需要为剩余的内部元组发出右连接元组。
							 */
							fc_node->mj_JoinState = EXEC_MJ_ENDOUTER;
							break;
						}
						/* 否则我们完成了。 */
						return NULL;
				}
				break;

				/*--------------------------------------------------------
				 * EXEC_MJ_TESTOUTER 如果新的外部元组与标记
				 * 元组满足合并条件，则我们知道在外部扫描中存在
				 * 重复项，因此我们必须将内部扫描恢复到标记元组，并继续将
				 * 新的外部元组与内部元组连接。
				 *
				 * 这是发生在
				 *						  外部 内部
				 *							4	  5  - 标记元组
				 *			 外部元组 -	5	  5
				 *		 新外部元组 -	5	  5
				 *							6	  8  - 内部元组
				 *							7	 12
				 *
				 *				新的外部元组 == 标记元组
				 *
				 * 如果外部元组未通过测试，则我们已经完成
				 * 对标记元组的处理，我们必须寻找
				 * 当前内部元组的匹配。因此我们将
				 * 继续跳过外部元组，直到外部 >= 内部
				 * (EXEC_MJ_SKIP_TEST)。
				 *
				 *		这是发生在
				 *
				 *						  外部 内部
				 *							5	  5  - 标记元组
				 *			 外部元组 -	5	  5
				 *		 新外部元组 -	6	  8  - 内部元组
				 *							7	 12
				 *
				 *				新的外部元组 > 标记元组
				 *
				 *---------------------------------------------------------
				 */
			case EXEC_MJ_TESTOUTER:
				MJ_printf("ExecMergeJoin: EXEC_MJ_TESTOUTER\n");

				/*
				 * 在这里，我们必须将外部元组与标记内部
				 * 元组进行比较。（我们可以忽略 MJEvalInnerValues 的结果，
				 * 因为标记内部元组肯定是可匹配的。）
				 */
				fc_innerTupleSlot = fc_node->mj_MarkedTupleSlot;
				(void) fc_MJEvalInnerValues(fc_node, fc_innerTupleSlot);

				fc_compareResult = fc_MJCompare(fc_node);
				MJ_DEBUG_COMPARE(fc_compareResult);

				if (fc_compareResult == 0)
				{
					/*
					 * 合并条件匹配，因此现在我们将内部
					 * 扫描位置恢复到第一个标记，并加入该元组
					 * （以及随后的任何元组）到新的外部元组。
					 *
					 * 如果我们能够确定不需要标记和恢复，
					 * 那么我们不必后退；当前的
					 * 内部元组已经是第一个可能的匹配。
					 *
					 * 注意：我们不需要担心被重新扫描的内部元组的
					 * MatchedInner 状态。我们知道它们都会
					 * 匹配这个新的外部元组，因此
					 * 不会作为填充元组被发出。这仅在
					 * 进行右连接或全连接时要求额外的 joinquals
					 * 为常量 —— 否则某些被重新扫描的元组可能
					 * 会未通过额外的 joinquals。
					 * 这显然不会发生在常量-真额外
					 * joinqual 的情况下，而常量-假情况
					 * 是通过强制合并条件永远不匹配来处理的，因此我们
					 * 从未到达这里。
					 */
					if (!fc_node->mj_SkipMarkRestore)
					{
						ExecRestrPos(fc_innerPlan);

						/*
						 * ExecRestrPos 可能应该给我们返回一个新的
						 * Slot，但由于它不这样做，使用标记的槽。
						 * （之前返回的 mj_InnerTupleSlot 不能
						 * 被假设持有所需的元组。）
						 */
						fc_node->mj_InnerTupleSlot = fc_innerTupleSlot;
						/* 我们不需要再次进行 MJEvalInnerValues */
					}

					fc_node->mj_JoinState = EXEC_MJ_JOINTUPLES;
				}
				else if (fc_compareResult > 0)
				{
					
/* ----------------
					 * 如果新的外部元组没有匹配到标记的内部元组，那么我们有如下情况：
					 *
					 *			 外部 内部
					 *			   4	 4	- 标记的元组
					 * 新的外部 - 5	 4
					 *			   6	 5	- 内部元组
					 *			   7
					 *
					 * 这意味着所有后续的外部元组将比我们标记的内部元组更大。
					 * 所以我们无需重新访问任何标记的元组，可以继续
					 * 寻找与当前内部元组的匹配。如果没有更多的内部元组，
					 * 则不可能有更多的匹配。
					 * ----------------
					 */
					fc_innerTupleSlot = fc_node->mj_InnerTupleSlot;

					/* 为当前内部元组重新加载比较数据 */
					switch (fc_MJEvalInnerValues(fc_node, fc_innerTupleSlot))
					{
						case MJEVAL_MATCHABLE:
							/* 继续与当前外部元组进行比较 */
							fc_node->mj_JoinState = EXEC_MJ_SKIP_TEST;
							break;
						case MJEVAL_NONMATCHABLE:

							/*
							 * 当前内部元组不可能与任何外部元组匹配；
							 * 这时更好地推进内部扫描而不是外部。
							 */
							fc_node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
							break;
						case MJEVAL_ENDOFJOIN:
							/* 没有更多的内部元组 */
							if (fc_doFillOuter)
							{
								/*
								 * 需要为剩余的外部元组发出左连接元组。
								 */
								fc_node->mj_JoinState = EXEC_MJ_ENDINNER;
								break;
							}
							/* 否则我们完成了。 */
							return NULL;
					}
				}
				else			/* compareResult < 0 不应该发生 */
					elog(ERROR, "mergejoin input data is out of order");
				break;

				/*----------------------------------------------------------
				 * EXEC_MJ_SKIP_TEST 意味着比较元组，如果它们不匹配，
				 * 跳过较小的元组。
				 *
				 * 例如：
				 *
				 *				外部 内部
				 *				  5		5
				 *				  5		5
				 * 外部元组 -  6		8  - 内部元组
				 *				  7    12
				 *				  8    14
				 *
				 * 我们必须推进外部扫描
				 * 直到找到外部 8。
				 *
				 * 另一方面：
				 *
				 *				外部 内部
				 *				  5		5
				 *				  5		5
				 * 外部元组 - 12		8  - 内部元组
				 *				 14    10
				 *				 17    12
				 *
				 * 我们必须推进内部扫描
				 * 直到找到内部 12。
				 *----------------------------------------------------------
				 */
			case EXEC_MJ_SKIP_TEST:
				MJ_printf("ExecMergeJoin: EXEC_MJ_SKIP_TEST\n");

				/*
				 * 在我们推进之前，确保当前元组不满足合并条件。
				 * 如果满足，那么我们更新标记元组的位置并去连接它们。
				 */
				fc_compareResult = fc_MJCompare(fc_node);
				MJ_DEBUG_COMPARE(fc_compareResult);

				if (fc_compareResult == 0)
				{
					if (!fc_node->mj_SkipMarkRestore)
						ExecMarkPos(fc_innerPlan);

					MarkInnerTuple(fc_node->mj_InnerTupleSlot, fc_node);

					fc_node->mj_JoinState = EXEC_MJ_JOINTUPLES;
				}
				else if (fc_compareResult < 0)
					fc_node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
				else
					/* compareResult > 0 */
					fc_node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
				break;

				/*
				 * EXEC_MJ_SKIPOUTER_ADVANCE：推进到已知不与任何内部元组连接的外部元组上。
				 *
				 * 在推进之前，我们检查是否必须为这个外部元组发出
				 * 外部连接填充元组。
				 */
			case EXEC_MJ_SKIPOUTER_ADVANCE:
				MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_ADVANCE\n");

				if (fc_doFillOuter && !fc_node->mj_MatchedOuter)
				{
					/*
					 * 生成一个虚假的连接元组，对于内部元组使用空值，
					 * 如果它通过非连接条件，则返回它。
					 */
					TupleTableSlot *fc_result;

					fc_node->mj_MatchedOuter = true;	/* 只做一次 */

					fc_result = fc_MJFillOuter(fc_node);
					if (fc_result)
						return fc_result;
				}

				/*
				 * 现在我们获取下一个外部元组（如果有）
				 */
				fc_outerTupleSlot = ExecProcNode(fc_outerPlan);
				fc_node->mj_OuterTupleSlot = fc_outerTupleSlot;
				MJ_DEBUG_PROC_NODE(fc_outerTupleSlot);
				fc_node->mj_MatchedOuter = false;

				/* 计算连接值并检查是否不可匹配 */
				switch (fc_MJEvalOuterValues(fc_node))
				{
					case MJEVAL_MATCHABLE:
						/* 对新元组进行测试，待比较当前内部元组 */
						fc_node->mj_JoinState = EXEC_MJ_SKIP_TEST;
						break;
					case MJEVAL_NONMATCHABLE:
						/* 不能匹配，因此获取下一个外部元组 */
						fc_node->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
						break;
					case MJEVAL_ENDOFJOIN:
						/* 没有更多的外部元组 */
						MJ_printf("ExecMergeJoin: end of outer subplan\n");
						fc_innerTupleSlot = fc_node->mj_InnerTupleSlot;
						if (fc_doFillInner && !TupIsNull(fc_innerTupleSlot))
						{
							/*
							 * 需要为剩余的内部元组发出右连接元组。
							 */
							fc_node->mj_JoinState = EXEC_MJ_ENDOUTER;
							break;
						}
						/* 否则我们完成了。 */
						return NULL;
				}
				break;

				/*
				 * EXEC_MJ_SKIPINNER_ADVANCE：推进到已知不与任何外部元组连接的内部元组上。
				 *
				 * 在推进之前，我们检查是否必须为这个内部元组发出
				 * 外部连接填充元组。
				 */
			case EXEC_MJ_SKIPINNER_ADVANCE:
				MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_ADVANCE\n");

				if (fc_doFillInner && !fc_node->mj_MatchedInner)
				{
					/*
					 * 生成一个假的连接元组，外部元组为null，如果它通过
					 * 非连接资格，则返回它。
					 */
					TupleTableSlot *fc_result;

					fc_node->mj_MatchedInner = true;	/* 只做一次 */

					fc_result = fc_MJFillInner(fc_node);
					if (fc_result)
						return fc_result;
				}

				/* 如果需要，标记后再推进 */
				if (fc_node->mj_ExtraMarks)
					ExecMarkPos(fc_innerPlan);

				/*
				 * 现在我们获取下一个内部元组，如果有的话
				 */
				fc_innerTupleSlot = ExecProcNode(fc_innerPlan);
				fc_node->mj_InnerTupleSlot = fc_innerTupleSlot;
				MJ_DEBUG_PROC_NODE(fc_innerTupleSlot);
				fc_node->mj_MatchedInner = false;

				/* 计算连接值并检查是否不可匹配 */
				switch (fc_MJEvalInnerValues(fc_node, fc_innerTupleSlot))
				{
					case MJEVAL_MATCHABLE:
						/* 继续与当前外部元组进行比较 */
						fc_node->mj_JoinState = EXEC_MJ_SKIP_TEST;
						break;
					case MJEVAL_NONMATCHABLE:

						/*
						 * 当前内部元组不可能与任何外部元组匹配；
						 * 更好地推进内部扫描而不是外部。
						 */
						fc_node->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
						break;
					case MJEVAL_ENDOFJOIN:
						/* 没有更多的内部元组 */
						MJ_printf("ExecMergeJoin: end of inner subplan\n");
						fc_outerTupleSlot = fc_node->mj_OuterTupleSlot;
						if (fc_doFillOuter && !TupIsNull(fc_outerTupleSlot))
						{
							/*
							 * 需要为剩余的外部元组发出左连接元组。
							 */
							fc_node->mj_JoinState = EXEC_MJ_ENDINNER;
							break;
						}
						/* 否则我们完成了。 */
						return NULL;
				}
				break;

				/*
				 * EXEC_MJ_ENDOUTER 意味着我们已经用完外部元组，但
				 * 正在进行右连接/全连接，因此必须对任何剩余
				 * 未匹配的内部元组进行空填充。
				 */
			case EXEC_MJ_ENDOUTER:
				MJ_printf("ExecMergeJoin: EXEC_MJ_ENDOUTER\n");

				Assert(fc_doFillInner);

				if (!fc_node->mj_MatchedInner)
				{
					/*
					 * 生成一个假的连接元组，外部元组为null，如果它通过
					 * 非连接资格，则返回它。
					 */
					TupleTableSlot *fc_result;

					fc_node->mj_MatchedInner = true;	/* 只做一次 */

					fc_result = fc_MJFillInner(fc_node);
					if (fc_result)
						return fc_result;
				}

				/* 如果需要，标记后再推进 */
				if (fc_node->mj_ExtraMarks)
					ExecMarkPos(fc_innerPlan);

				/*
				 * 现在我们获取下一个内部元组，如果有的话
				 */
				fc_innerTupleSlot = ExecProcNode(fc_innerPlan);
				fc_node->mj_InnerTupleSlot = fc_innerTupleSlot;
				MJ_DEBUG_PROC_NODE(fc_innerTupleSlot);
				fc_node->mj_MatchedInner = false;

				if (TupIsNull(fc_innerTupleSlot))
				{
					MJ_printf("ExecMergeJoin: end of inner subplan\n");
					return NULL;
				}

				/* 否则保持在 ENDOUTER 状态并处理下一个元组。 */
				break;

				/*
				 * EXEC_MJ_ENDINNER 意味着我们已经用完了内部元组，但
				 * 正在进行左/全连接，因此必须对
				 * 任何剩余的未匹配的外部元组填充空值。
				 */
			case EXEC_MJ_ENDINNER:
				MJ_printf("ExecMergeJoin: EXEC_MJ_ENDINNER\n");

				Assert(fc_doFillOuter);

				if (!fc_node->mj_MatchedOuter)
				{
					/*
					 * 生成一个虚假的连接元组，对于内部元组使用空值，
					 * 如果它通过非连接条件，则返回它。
					 */
					TupleTableSlot *fc_result;

					fc_node->mj_MatchedOuter = true;	/* 只做一次 */

					fc_result = fc_MJFillOuter(fc_node);
					if (fc_result)
						return fc_result;
				}

				/*
				 * 现在我们获取下一个外部元组（如果有）
				 */
				fc_outerTupleSlot = ExecProcNode(fc_outerPlan);
				fc_node->mj_OuterTupleSlot = fc_outerTupleSlot;
				MJ_DEBUG_PROC_NODE(fc_outerTupleSlot);
				fc_node->mj_MatchedOuter = false;

				if (TupIsNull(fc_outerTupleSlot))
				{
					MJ_printf("ExecMergeJoin: end of outer subplan\n");
					return NULL;
				}

				/* 否则保持在 ENDINNER 状态并处理下一个元组。 */
				break;

				/*
				 * 损坏的状态值？
				 */
			default:
				elog(ERROR, "unrecognized mergejoin state: %d",
					 (int) fc_node->mj_JoinState);
		}
	}
}

/* ----------------------------------------------------------------
 *		ExecInitMergeJoin
 * ----------------------------------------------------------------
 */
MergeJoinState *
ExecInitMergeJoin(MergeJoin *fc_node, EState *fc_estate, int fc_eflags)
{
	MergeJoinState *fc_mergestate;
	TupleDesc	fc_outerDesc,
				fc_innerDesc;
	const TupleTableSlotOps *fc_innerOps;

	/* 检查不支持的标志 */
	Assert(!(fc_eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));

	MJ1_printf("ExecInitMergeJoin: %s\n",
			   "initializing node");

	/*
	 * 创建状态结构
	 */
	fc_mergestate = makeNode(MergeJoinState);
	fc_mergestate->js.ps.plan = (Plan *) fc_node;
	fc_mergestate->js.ps.state = fc_estate;
	fc_mergestate->js.ps.ExecProcNode = fc_ExecMergeJoin;
	fc_mergestate->js.jointype = fc_node->join.jointype;
	fc_mergestate->mj_ConstFalseJoin = false;

	/*
	 * 杂项初始化
	 *
	 * 为节点创建表达式上下文
	 */
	ExecAssignExprContext(fc_estate, &fc_mergestate->js.ps);

	/*
	 * 我们需要两个额外的上下文，在这些上下文中我们可以计算
	 * 来自左侧和右侧输入元组的连接表达式。节点的常规
	 * 上下文不适用，因为它重置得太频繁。
	 */
	fc_mergestate->mj_OuterEContext = CreateExprContext(fc_estate);
	fc_mergestate->mj_InnerEContext = CreateExprContext(fc_estate);

	/*
	 * 初始化子节点
	 *
	 * 内部子节点必须支持 MARK/RESTORE，除非我们已经检测到不需要这样。
	 * 请注意，如果存在非合并子句连接条件，则 skip_mark_restore 绝不可设置，
	 * 因为逻辑将无法正常工作。
	 */
	Assert(fc_node->join.joinqual == NIL || !fc_node->skip_mark_restore);
	fc_mergestate->mj_SkipMarkRestore = fc_node->skip_mark_restore;

	outerPlanState(fc_mergestate) = ExecInitNode(outerPlan(fc_node), fc_estate, fc_eflags);
	fc_outerDesc = ExecGetResultType(outerPlanState(fc_mergestate));
	innerPlanState(fc_mergestate) = ExecInitNode(innerPlan(fc_node), fc_estate,
											  fc_mergestate->mj_SkipMarkRestore ?
											  fc_eflags :
											  (fc_eflags | EXEC_FLAG_MARK));
	fc_innerDesc = ExecGetResultType(innerPlanState(fc_mergestate));

	/*
	 * 对于某些类型的内部子节点，每当我们经过一个
	 * 永远不会返回的内部元组时，发出 MARK 是有利的。
	 * 对于其他类型，在一个我们无法返回的元组上进行 MARK
	 * 是浪费周期。检测适用哪种情况，如果我们想要
	 * 发出“多余”的 MARK 调用，则设置 mj_ExtraMarks。
	 *
	 * 当前，只有 Material 需要额外的 MARK，
	 * 并且仅在 eflags 未指定 REWIND 时才会有帮助。
	 *
	 * 请注意，对于 IndexScan 和 IndexOnlyScan，* 必须 * 
	 * 不设置 mj_ExtraMarks；否则，我们可能会试图在
	 * 第一个内部元组之前设置标记，这些操作不支持。
	 */
	if (IsA(innerPlan(fc_node), Material) &&
		(fc_eflags & EXEC_FLAG_REWIND) == 0 &&
		!fc_mergestate->mj_SkipMarkRestore)
		fc_mergestate->mj_ExtraMarks = true;
	else
		fc_mergestate->mj_ExtraMarks = false;

	/*
	 * 初始化结果槽、类型和投影。
	 */
	ExecInitResultTupleSlotTL(&fc_mergestate->js.ps, &TTSOpsVirtual);
	ExecAssignProjectionInfo(&fc_mergestate->js.ps, NULL);

	/*
	 * 元组表初始化
	 */
	fc_innerOps = ExecGetResultSlotOps(innerPlanState(fc_mergestate), NULL);
	fc_mergestate->mj_MarkedTupleSlot = ExecInitExtraTupleSlot(fc_estate, fc_innerDesc,
															fc_innerOps);

	/*
	 * 初始化子表达式
	 */
	fc_mergestate->js.ps.qual =
		ExecInitQual(fc_node->join.plan.qual, (PlanState *) fc_mergestate);
	fc_mergestate->js.joinqual =
		ExecInitQual(fc_node->join.joinqual, (PlanState *) fc_mergestate);
	/* mergeclauses 在下面处理 */

	/*
	 * 检测是否只需考虑第一个匹配的内层元组
	 */
	fc_mergestate->js.single_match = (fc_node->join.inner_unique ||
								   fc_node->join.jointype == JOIN_SEMI);

	/* 如有需要，设置外连接的空元组 */
	switch (fc_node->join.jointype)
	{
		case JOIN_INNER:
		case JOIN_SEMI:
			fc_mergestate->mj_FillOuter = false;
			fc_mergestate->mj_FillInner = false;
			break;
		case JOIN_LEFT:
		case JOIN_ANTI:
			fc_mergestate->mj_FillOuter = true;
			fc_mergestate->mj_FillInner = false;
			fc_mergestate->mj_NullInnerTupleSlot =
				ExecInitNullTupleSlot(fc_estate, fc_innerDesc, &TTSOpsVirtual);
			break;
		case JOIN_RIGHT:
			fc_mergestate->mj_FillOuter = false;
			fc_mergestate->mj_FillInner = true;
			fc_mergestate->mj_NullOuterTupleSlot =
				ExecInitNullTupleSlot(fc_estate, fc_outerDesc, &TTSOpsVirtual);

			/*
			 * 无法处理具有非常量额外连接条件的右连接或全连接。
			 * 这应该已经被规划器捕获。
			 */
			if (!fc_check_constant_qual(fc_node->join.joinqual,
									 &fc_mergestate->mj_ConstFalseJoin))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("RIGHT JOIN is only supported with merge-joinable join conditions")));
			break;
		case JOIN_FULL:
			fc_mergestate->mj_FillOuter = true;
			fc_mergestate->mj_FillInner = true;
			fc_mergestate->mj_NullOuterTupleSlot =
				ExecInitNullTupleSlot(fc_estate, fc_outerDesc, &TTSOpsVirtual);
			fc_mergestate->mj_NullInnerTupleSlot =
				ExecInitNullTupleSlot(fc_estate, fc_innerDesc, &TTSOpsVirtual);

			/*
			 * 无法处理具有非常量额外连接条件的右连接或全连接。
			 * 这应该已经被规划器捕获。
			 */
			if (!fc_check_constant_qual(fc_node->join.joinqual,
									 &fc_mergestate->mj_ConstFalseJoin))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("FULL JOIN is only supported with merge-joinable join conditions")));
			break;
		default:
			elog(ERROR, "unrecognized join type: %d",
				 (int) fc_node->join.jointype);
	}

	/*
	 * 预处理合并子句
	 */
	fc_mergestate->mj_NumClauses = list_length(fc_node->mergeclauses);
	fc_mergestate->mj_Clauses = fc_MJExamineQuals(fc_node->mergeclauses,
											fc_node->mergeFamilies,
											fc_node->mergeCollations,
											fc_node->mergeStrategies,
											fc_node->mergeNullsFirst,
											(PlanState *) fc_mergestate);

	/*
	 * 初始化连接状态
	 */
	fc_mergestate->mj_JoinState = EXEC_MJ_INITIALIZE_OUTER;
	fc_mergestate->mj_MatchedOuter = false;
	fc_mergestate->mj_MatchedInner = false;
	fc_mergestate->mj_OuterTupleSlot = NULL;
	fc_mergestate->mj_InnerTupleSlot = NULL;

	/*
	 * 初始化成功
	 */
	MJ1_printf("ExecInitMergeJoin: %s\n",
			   "node initialized");

	return fc_mergestate;
}

/* ----------------------------------------------------------------
 *		ExecEndMergeJoin
 *
 * 旧注释
 *		释放通过 C 例程分配的存储。
 * ----------------------------------------------------------------
 */
void ExecEndMergeJoin(MergeJoinState *fc_node)
{
	MJ1_printf("ExecEndMergeJoin: %s\n",
			   "ending node processing");

	/*
	 * 释放表达式上下文
	 */
	ExecFreeExprContext(&fc_node->js.ps);

	/*
	 * 清空元组表
	 */
	ExecClearTuple(fc_node->js.ps.ps_ResultTupleSlot);
	ExecClearTuple(fc_node->mj_MarkedTupleSlot);

	/*
	 * 关闭子计划
	 */
	ExecEndNode(innerPlanState(fc_node));
	ExecEndNode(outerPlanState(fc_node));

	MJ1_printf("ExecEndMergeJoin: %s\n",
			   "node processing ended");
}

void ExecReScanMergeJoin(MergeJoinState *fc_node)
{
	ExecClearTuple(fc_node->mj_MarkedTupleSlot);

	fc_node->mj_JoinState = EXEC_MJ_INITIALIZE_OUTER;
	fc_node->mj_MatchedOuter = false;
	fc_node->mj_MatchedInner = false;
	fc_node->mj_OuterTupleSlot = NULL;
	fc_node->mj_InnerTupleSlot = NULL;

	/*
	 * 如果子节点的 chgParam 不为空，则将在第一个 ExecProcNode
	 * 中重新扫描计划。
	 */
	if (fc_node->js.ps.lefttree->chgParam == NULL)
		ExecReScan(fc_node->js.ps.lefttree);
	if (fc_node->js.ps.righttree->chgParam == NULL)
		ExecReScan(fc_node->js.ps.righttree);
}
